#include <exception>
#include <mutex>
#include <algorithm>

/*

    基于锁实现的线程安全的栈（后入先出）

*/

struct empty_stack: std::exception
{
  const char* what() const throw();
};

template<typename T>
class threadsafe_stack
{
private:
    std::stack<T> data;
    // 互斥量m可保证线程安全，对每个成员函数进行加锁保护。保证在同一时间内，只有一个线程可以访问到数据。
    mutable std::mutex m;
public:
    // 没有显示的初始化data和m, 安装C++的规则
    // 调用这些对象的默认初始化函数初始化
    threadsafe_stack(){}

    // 支持拷贝构造
    threadsafe_stack(const threadsafe_stack& other)
    {
        std::lock_guard<std::mutex> lock(other.m);
        data=other.data;
    }

    // 不允许赋值
    threadsafe_stack& operator=(const threadsafe_stack&) = delete;

    void push(T new_value)
    {
        std::lock_guard<std::mutex> lock(m);
        // 对data.push()的调用可能会抛出一个异常，不是拷贝/移动数据，就是内存不足。
        // std::stack<>都能保证其安全性
        data.push(std::move(new_value));
    }
    std::shared_ptr<T> pop()
    {
        std::lock_guard<std::mutex> lock(m);
        // pop()中可能会抛出empty_stack异常，数据没有修改，是安全的。
        if(data.empty())
            throw empty_stack();

        // 创建res时，也可能会抛出异常，两个原因：std::make_shared无法分配出足够的
        // 内存去创建新对象，并且内部数据需要引用新对象；或者在拷贝或移动构造到新分配的内存中时抛出异常。
        // 两种情况下，C++运行时库和标准库能确保不会出现内存泄露，并且新创建的对象(如果有的话)都能正确销毁。
        // 因为没有对栈进行任何修改，所以没问题。
        std::shared_ptr<T> const res(std::make_shared<T>(std::move(data.top())));
        // 当调用data.pop()时，能保证不抛出异常并返回结果，
        data.pop();
        return res;
    }
    void pop(T& value)
    {
        std::lock_guard<std::mutex> lock(m);
        if(data.empty())
            throw empty_stack();
        // 在拷贝赋值或移动赋值时会抛出异常
        // 当构造新对象和std::shared_ptr实例时都不会抛出异常
        value=std::move(data.top());  // 5
        data.pop();  // 6
    }
    bool empty() const
    {
        std::lock_guard<std::mutex> lock(m);
        return data.empty();
    }
};